#include <QGraphicsScene>
#include <QTimer>
#include <QList>

#include "UITableViewDataSource.h"
#include "UITableViewCell.h"
#include "UITableView.h"

class UITableViewPrivate {
    public:
        UITableViewDataSource *dataSource;

        QList<QGraphicsItem *> visibleRows;
        QList<QGraphicsItem *> prevCache;
        QList<QGraphicsItem *> nextCache;

        QPointF scrollOffset;
        qreal fullHeight;

    public:
        void scrollUp (UITableView *tableView, qreal yDelta);
        void scrollDown (UITableView *tableView, qreal yDelta);
};

void UITableViewPrivate::scrollUp (UITableView *tableView, qreal yDelta) {
    if (nextCache.size() < 1)
        return;

    qreal visibleHeight = tableView->boundingRect().height();

    // Remove all the Hidden Items
    QGraphicsItem *pItem = visibleRows.first();
    while ((pItem->y() + yDelta + pItem->boundingRect().height()) < 0) {
        pItem->setVisible(false);
        prevCache.insert(0, visibleRows.takeFirst());

        pItem = visibleRows.first();
    }

    // Move Up Visible Items
    foreach (QGraphicsItem *item, visibleRows)
        item->moveBy(0.0f, yDelta);

    // Add Bottom Visible Items
    pItem = visibleRows.last();
    qreal y = pItem->y() + pItem->boundingRect().height();
    while (y < visibleHeight) {
        QGraphicsItem *item = nextCache.takeFirst();
        item->setVisible(true);
        item->setPos(0.0f, y);

        visibleRows.append(item);
        y += item->boundingRect().height();            
    }
}

void UITableViewPrivate::scrollDown (UITableView *tableView, qreal yDelta) {
    qreal visibleHeight = tableView->boundingRect().height();

    // Remove al the Hidden Items
    QGraphicsItem *pItem = visibleRows.last();
    while ((pItem->y() + yDelta) > visibleHeight) {
        visibleRows.last()->setVisible(false);
        nextCache.insert(0, visibleRows.takeLast());

        pItem = visibleRows.last();
    }

    // Move Down Visible Items
    foreach (QGraphicsItem *item, visibleRows)
        item->moveBy(0.0f, yDelta);

    // Add Top Visible Items
    qreal y = visibleRows.first()->y();
    while (prevCache.size() > 0 && y > 0) {
        QGraphicsItem *item = prevCache.takeFirst();
        qreal itemHeight = item->boundingRect().height();
        y -= itemHeight;

        item->setPos(0.0f, y);
        item->setVisible(true);
        
        visibleRows.insert(0, item);
    }
}

UITableView::UITableView (QGraphicsItem *parent)
    : QObject(), UIView(parent), d(new UITableViewPrivate)
{
    setScrollEnabled(true);

    d->dataSource = NULL;
    d->fullHeight = 0.0f;
    d->scrollOffset = QPointF(0.0f, 0.0f);
    QTimer::singleShot(0, this, SLOT(reloadData()));
}

UITableView::~UITableView() {
    if (d->dataSource != NULL)
        delete d->dataSource;

    delete d;
}

UITableViewDataSource *UITableView::dataSource (void) const {
    return(d->dataSource);
}

void UITableView::setDataSource (UITableViewDataSource *dataSource) {
    d->dataSource = dataSource;
}

void UITableView::reloadData (void) {
    if (d->dataSource == NULL)
        return;

    // Clear Prev Data
    clearData();

    qreal visibleHeight = boundingRect().height();
    d->fullHeight = 0.0f;

    float y = 0;
    UITableViewCell *cell;
    uint sectionsNr = d->dataSource->tableViewNumberOfSections(this);
    for (uint i = 0; i < sectionsNr; ++i) {
        // Add Header Title
        cell = d->dataSource->tableViewCellForHeaderInSection(this, i);
        if (cell != NULL) {
            if (y > visibleHeight) {
                cell->setVisible(false);
                d->nextCache.append(cell);
            } else {
                cell->setPos(0, y);
                d->visibleRows.append(cell);

                y += cell->boundingRect().height();
            }

            d->fullHeight += cell->boundingRect().height();
        }

        // Add Rows Cell
        uint rowsNr = d->dataSource->tableViewNumberOfRowsInSection(this, i);
        for (uint j = 0; j < rowsNr; ++j) {
            cell = d->dataSource->tableViewCellForRow(this, i, j);
            if (cell == NULL) continue;

            if (y > visibleHeight) {
                cell->setVisible(false);
                d->nextCache.append(cell);
            } else {
                cell->setPos(0, y);
                d->visibleRows.append(cell);

                y += cell->boundingRect().height();
            }

            d->fullHeight += cell->boundingRect().height();
        }

        // Add Footer Title
        cell = d->dataSource->tableViewCellForFooterInSection(this, i);
        if (cell != NULL) {
            if (y > visibleHeight) {
                cell->setVisible(false);
                d->nextCache.append(cell);
            } else {
                cell->setPos(0, y);
                d->visibleRows.append(cell);

                y += cell->boundingRect().height();
            }

            d->fullHeight += cell->boundingRect().height();
        }
    }    
}

void UITableView::clearData (void) {
    QGraphicsScene *tableScene = scene();
    QGraphicsItem *item;

    // Stop Updates
    setUpdatesEnabled(false);

    // Erase Visible Rows
    QList<QGraphicsItem *>::iterator i = d->visibleRows.begin();
    while (i != d->visibleRows.end()) {
        item = *i;
        tableScene->removeItem(item);
        d->visibleRows.erase(i);
        delete item;
    }

    // Erase Prev Cache
    i = d->prevCache.begin();
    while (i != d->prevCache.end()) {
        item = *i;
        d->prevCache.erase(i);
        delete item;
    }

    // Erase Next Cache
    i = d->nextCache.begin();
    while (i != d->nextCache.end()) {
        item = *i;
        d->prevCache.erase(i);
        delete item;
    }

    // Restart Updates
    setUpdatesEnabled(true);
}

QPointF UITableView::scrollOffset (void) const {
    return(d->scrollOffset);
}

void UITableView::setScrollOffset (const QPointF& pos) {
    if (pos.y() < -2.0f || pos.y() > (d->fullHeight + 2.0f))
        return;

    qreal yDelta = d->scrollOffset.y() - pos.y();
    d->scrollOffset = pos;
    if (yDelta != 0) {
        if (yDelta < 0)
            d->scrollUp(this, yDelta);
        else
            d->scrollDown(this, yDelta);
    }
}

